\index.html -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>FrankMocap 网页应用</title>
    <style>
        body {
            font-family: 'Arial', sans-serif;
            background-color: #f8f9fa;
            margin: 0;
            padding: 0;
            color: #333;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            padding: 20px;
        }
        header {
            background-color: #343a40;
            color: white;
            padding: 20px 0;
            text-align: center;
            margin-bottom: 30px;
        }
        h1 {
            margin: 0;
        }
        .main-content {
            display: flex;
            flex-wrap: wrap;
            gap: 30px;
        }
        .control-panel {
            flex: 1;
            min-width: 300px;
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
        }
        .display-panel {
            flex: 2;
            min-width: 640px;
            background-color: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
            display: flex;
            flex-direction: column;
        }
        .option-group {
            margin-bottom: 25px;
        }
        .option-title {
            font-weight: bold;
            margin-bottom: 10px;
        }
        .radio-group {
            margin-left: 10px;
        }
        .radio-option {
            display: block;
            margin-bottom: 10px;
        }
        .video-container {
            position: relative;
            width: 100%;
            overflow: hidden;
            margin-bottom: 20px;
        }
        video, canvas {
            width: 100%;
            height: auto;
            max-height: 480px;
            background-color: #222;
            border-radius: 4px;
        }
        .canvas-stack {
            position: relative;
            width: 100%;
            height: 480px;
        }
        .canvas-stack canvas {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
        }
        #originalCanvas {
            z-index: 1;
        }
        #resultCanvas {
            z-index: 2;
        }
        button {
            background-color: #007bff;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 16px;
            margin-right: 10px;
            transition: background-color 0.3s;
        }
        button:hover {
            background-color: #0069d9;
        }
        button:disabled {
            background-color: #6c757d;
            cursor: not-allowed;
        }
        .status {
            margin-top: 20px;
            font-style: italic;
            color: #6c757d;
        }
        .error {
            color: #dc3545;
        }
        .fps-display {
            position: absolute;
            top: 10px;
            left: 10px;
            background-color: rgba(0, 0, 0, 0.6);
            color: white;
            padding: 5px 10px;
            border-radius: 4px;
            font-size: 14px;
            z-index: 3;
        }
    </style>
</head>
<body>
    <header>
        <div class="container">
            <h1>FrankMocap 网页应用</h1>
            <p>实时动作捕捉系统</p>
        </div>
    </header>

    <div class="container">
        <div class="main-content">
            <div class="control-panel">
                <div class="option-group">
                    <div class="option-title">选择捕捉模式</div>
                    <div class="radio-group">
                        <label class="radio-option">
                            <input type="radio" name="mode" value="frank" checked> 完整捕捉 (身体+手部)
                        </label>
                        <label class="radio-option">
                            <input type="radio" name="mode" value="body"> 仅身体捕捉
                        </label>
                        <label class="radio-option">
                            <input type="radio" name="mode" value="hand"> 仅手部捕捉
                        </label>
                    </div>
                </div>

                <div class="option-group">
                    <div class="option-title">渲染设置</div>
                    <div class="radio-group">
                        <label class="radio-option">
                            <input type="checkbox" id="showSkeleton" checked> 显示骨骼
                        </label>
                        <label class="radio-option">
                            <input type="checkbox" id="showMesh" checked> 显示网格
                        </label>
                    </div>
                </div>

                <div class="option-group">
                    <button id="startButton">开始捕捉</button>
                    <button id="stopButton" disabled>停止</button>
                </div>

                <div class="status" id="statusMessage">准备就绪</div>
            </div>

            <div class="display-panel">
                <div class="canvas-stack">
                    <canvas id="originalCanvas"></canvas>
                    <canvas id="resultCanvas"></canvas>
                    <div class="fps-display" id="fpsCounter">FPS: 0</div>
                </div>
            </div>
        </div>
    </div>

    <script>
        // 全局变量
        let video;
        let mediaStream;
        let websocket;
        let originalCanvas, originalCtx;
        let resultCanvas, resultCtx;
        let animationId;
        let isProcessing = false;
        let lastFrameTime = 0;
        let frameCount = 0;
        let lastFpsUpdate = 0;
        
        // 初始化
        document.addEventListener('DOMContentLoaded', () => {
            // 获取元素
            originalCanvas = document.getElementById('originalCanvas');
            originalCtx = originalCanvas.getContext('2d');
            resultCanvas = document.getElementById('resultCanvas');
            resultCtx = resultCanvas.getContext('2d');
            const startButton = document.getElementById('startButton');
            const stopButton = document.getElementById('stopButton');
            const statusMessage = document.getElementById('statusMessage');
            const fpsCounter = document.getElementById('fpsCounter');
            
            // 事件监听
            startButton.addEventListener('click', startCapture);
            stopButton.addEventListener('click', stopCapture);
            
            // 设置画布尺寸
            setCanvasSize();
            window.addEventListener('resize', setCanvasSize);
        });
        
        // 设置画布尺寸
        function setCanvasSize() {
            const container = document.querySelector('.canvas-stack');
            const width = container.offsetWidth;
            const height = container.offsetHeight;
            
            originalCanvas.width = width;
            originalCanvas.height = height;
            resultCanvas.width = width;
            resultCanvas.height = height;
        }
        
        // 开始捕捉
        async function startCapture() {
            try {
                document.getElementById('statusMessage').textContent = '正在初始化摄像头...';
                document.getElementById('startButton').disabled = true;
                
                // 获取摄像头访问权限
                mediaStream = await navigator.mediaDevices.getUserMedia({
                    video: {
                        width: { ideal: 640 },
                        height: { ideal: 480 },
                        facingMode: 'user'
                    }
                });
                
                // 创建视频元素
                video = document.createElement('video');
                video.srcObject = mediaStream;
                video.setAttribute('playsinline', true);
                await video.play();
                
                // 连接WebSocket
                connectWebSocket();
                
                // 开始处理视频帧
                lastFrameTime = performance.now();
                lastFpsUpdate = performance.now();
                frameCount = 0;
                requestAnimationFrame(processFrame);
                
                // 更新UI
                document.getElementById('startButton').disabled = true;
                document.getElementById('stopButton').disabled = false;
                document.getElementById('statusMessage').textContent = '捕捉进行中...';
                
            } catch (error) {
                console.error('Error starting capture:', error);
                document.getElementById('statusMessage').textContent = `错误: ${error.message}`;
                document.getElementById('statusMessage').classList.add('error');
                document.getElementById('startButton').disabled = false;
            }
        }
        
        // 停止捕捉
        function stopCapture() {
            // 停止动画
            if (animationId) {
                cancelAnimationFrame(animationId);
                animationId = null;
            }
            
            // 停止视频流
            if (mediaStream) {
                mediaStream.getTracks().forEach(track => track.stop());
                mediaStream = null;
            }
            
            // 关闭WebSocket
            if (websocket && websocket.readyState === WebSocket.OPEN) {
                websocket.close();
            }
            
            // 清除画布
            originalCtx.clearRect(0, 0, originalCanvas.width, originalCanvas.height);
            resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height);
            
            // 更新UI
            document.getElementById('startButton').disabled = false;
            document.getElementById('stopButton').disabled = true;
            document.getElementById('statusMessage').textContent = '捕捉已停止';
            document.getElementById('fpsCounter').textContent = 'FPS: 0';
        }
        
        // 连接WebSocket
        function connectWebSocket() {
            // 确定WebSocket URL
            const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
            const wsUrl = `${protocol}//${location.host}/ws`;
            
            websocket = new WebSocket(wsUrl);
            
            websocket.onopen = function() {
                console.log('WebSocket连接已建立');
                document.getElementById('statusMessage').textContent = '已连接到服务器';
            };
            
            websocket.onerror = function(error) {
                console.error('WebSocket错误:', error);
                document.getElementById('statusMessage').textContent = '服务器连接错误';
                document.getElementById('statusMessage').classList.add('error');
            };
            
            websocket.onclose = function() {
                console.log('WebSocket连接已关闭');
                if (document.getElementById('startButton').disabled) {
                    document.getElementById('statusMessage').textContent = '服务器连接已断开';
                }
            };
            
            websocket.onmessage = function(event) {
                // 处理服务器返回的数据
                const data = JSON.parse(event.data);
                displayResult(data);
                isProcessing = false;
            };
        }
        
        // 处理视频帧
        function processFrame() {
            animationId = requestAnimationFrame(processFrame);
            
            // 计算FPS
            const now = performance.now();
            frameCount++;
            
            // 每秒更新FPS显示
            if (now - lastFpsUpdate >= 1000) {
                const fps = Math.round((frameCount * 1000) / (now - lastFpsUpdate));
                document.getElementById('fpsCounter').textContent = `FPS: ${fps}`;
                frameCount = 0;
                lastFpsUpdate = now;
            }
            
            // 检查WebSocket连接
            if (!websocket || websocket.readyState !== WebSocket.OPEN) {
                return;
            }
            
            // 如果正在处理前一帧，则跳过
            if (isProcessing) {
                return;
            }
            
            // 绘制原始视频帧
            originalCtx.drawImage(video, 0, 0, originalCanvas.width, originalCanvas.height);
            
            // 获取当前选择的模式
            const modeElements = document.getElementsByName('mode');
            let selectedMode = 'frank';
            for (const element of modeElements) {
                if (element.checked) {
                    selectedMode = element.value;
                    break;
                }
            }
            
            // 将视频帧转换为base64
            const dataUrl = originalCanvas.toDataURL('image/jpeg', 0.7);
            
            // 发送到服务器
            try {
                websocket.send(JSON.stringify({
                    mode: selectedMode,
                    image: dataUrl
                }));
                isProcessing = true;
            } catch (error) {
                console.error('Error sending frame:', error);
                isProcessing = false;
            }
        }
        
        // 显示处理结果
        function displayResult(data) {
            if (!data.image) {
                return;
            }
            
            // 加载结果图像
            const img = new Image();
            img.onload = function() {
                resultCtx.clearRect(0, 0, resultCanvas.width, resultCanvas.height);
                resultCtx.drawImage(img, 0, 0, resultCanvas.width, resultCanvas.height);
                
                // 如果有关键点数据，可以在这里绘制额外的信息
                if (data.keypoints && document.getElementById('showSkeleton').checked) {
                    drawKeypoints(data.keypoints);
                }
            };
            img.src = data.image;
        }
        
        // 绘制关键点
        function drawKeypoints(keypoints) {
            // 实现关键点绘制
            const scaleX = resultCanvas.width / 640;
            const scaleY = resultCanvas.height / 480;
            
            resultCtx.fillStyle = 'red';
            resultCtx.strokeStyle = 'yellow';
            resultCtx.lineWidth = 2;
            
            for (let i = 0; i < keypoints.length; i++) {
                const [x, y] = keypoints[i];
                
                // 画点
                resultCtx.beginPath();
                resultCtx.arc(x * scaleX, y * scaleY, 4, 0, 2 * Math.PI);
                resultCtx.fill();
                
                // 连接关键点（简化示例）
                // 实际应用中应该根据SMPL或SMPLX模型的骨骼结构连接特定的关键点
                if (i > 0) {
                    resultCtx.beginPath();
                    resultCtx.moveTo(keypoints[i-1][0] * scaleX, keypoints[i-1][1] * scaleY);
                    resultCtx.lineTo(x * scaleX, y * scaleY);
                    resultCtx.stroke();
                }
            }
        }
    </script>
</body>
</html>